Skip to main content

lab0

CS3210 共安排了六个 lab,第一个 lab 就是跟着Rust By Example学习 rust 的基础知识,然后通过rustlings检验学习成果,课程要求大约花费 30~40 小时完成 Rust By Example 的前 18 章,还是挺吃时间的。

目前我对于 rust 的开发环境并不了解,为了防止因为版本不同造成错误,我通过课程提供的 rustup 脚本安装了开发环境,版本号 rustc 1.37.0-nightly (2019-06-30)。 从目前学习的内容来看 rust 与 c 有很大的相似点,所以我准备以对比的方式记录 rust

  • rustc 是 rust 的编译器,rust 语言扩展名为.rs,rust 的入口函数为fn main(){}

  • rust 的注释语法和 c 相同,//是单行注释,/**/是多行注释,不同的是 rust 的多行注释支持嵌套

  • rust 的标准输入输出库为std::fmt(还不知道为什么用宏实现),格式化输出同样使用包含转义字符的字符串,但语法与 c 不同,功能也更强大,给出一个例子printf("%02.3f%d", a, b) print!("{first:02.3f}{second}", second=b, first=a)

  • 结构体默认和 c 相同无法直接输出,但是可以通过实现fmt::Display来实现类似toString的功能,也可以通过derive自动实现。

  • rust 的数据类型也都是经典组合了,基础数据类型从 i8、u8 到 i128、u128,还有 f32、f64,四字节 char,bool,isize,usize;组合数据类型数组元组切片枚举结构体一应俱全

  • rust 的运算符和 c 一致,+-*/%!|^&>><<

  • rust 的枚举实现了 c 语言枚举和共用体两个特性,既可以枚举又可以存储变量,类似 switch 的match语句也让枚举处理更加优美,也让枚举变成了一个非常强大的工具。

  • typedef 现在 rust 中以关键字type呈现,但我还没仔细分析有没有什么差异。

  • rust 中的变量声明时,默认为可以接受非静态值的不可变变量,mut关键字使变量可变,const关键字使变量仅能接受静态值,这与 c 的处理方式完全不同。

  • 编译器会对未使用的变量抛出 warning,但以下划线开头的变量不会

  • rust 对变量作用域和二次声明提出了shadow的概念,简单来说就是let语句会覆盖本代码块和外层代码块的同名变量,对覆盖前后的变量类型、可变性没有要求,在本代码块中被覆盖的变量无法恢复,退出本代码块后被覆盖的外层变量会恢复(就是个作用域问题)。在造成shadowlet语句中可以使用被覆盖变量的值。

  • rust 不提倡对变量先声明而不初始化,编译器会对可能的未初始化而使用的情况抛出 error

  • rust 不进行隐式类型转换,显式类型转换的关键字为as

  • rust 的数值字面量可以用类型名修饰,比如1u8就是八位无符号整数 1。

  • 在声明变量时可以使用:显式声明类型,但是文档对编译器的自动类型推断非常自信

  • std::convert::From std::convert::TryFrom可以实现自定义类型的类型转换函数,后者支持异常处理。fmt::Display如上一篇中猜测的会自动实现to_string函数

  • rust 将代码块作为表达式处理,而且代码块的反大括号后面必须有分号。代码块的返回值取决于最后一行代码,若最后一行没有分号则会返回最后一行的值,若有则返回()。所以代码块是可以用来赋值的,值得注意的是,下面的控制语句中的代码块同样适用这个规则

  • rust 很自然地删掉了 c 语言中的switch语法,提供了更复杂但也更好用的match语法。除此之外还有常见的if-else while for-in等。

  • if-else大概是最好懂的语法了,除了布尔表达式不需要圆括号、代码块必须有大括号之外没有什么特别之处

  • while的额外特性也不多,可以使用例如'label: while true { break 'label; }的语法退出特定层级的循环。loop就是while true的语法糖

  • for-in虽然和 c 有差别但和其他现代语言没有太大差别,举个例子for n in 1..101 {},但是令我比较迷惑的是这个用例for n in 1..=100 {}会将原本的左闭右开区间变成左闭右闭区间,两个例子都是循环到 100,不太明白这是糖还是有别的目的。for-in还支持迭代器,但是现在看不太懂等后面章节吧

  • match的语法看得我头大,所以我决定直接上代码

  • if let以我的理解就是一个特殊的if语句,布尔表达式是一个let语句,该语句会像match一样解构类型,如果解构成功则执行代码块,失败则跳转到else

  • while let同理,在布尔表达式中尝试解构但代码块从if let的选择控制变成了循环控制

  • 定义函数的关键词是fn,声明返回值的关键词是->位于函数体前。给个例子fn foo(bar: i32) -> i32 {},和前文提到的代码块类似,函数体最后一行代码不加分号即视为返回值的表达式,在任意位置都可以通过return关键词返回。

  • 通过impl关键词可以为结构体添加函数,即方法(method),方法可以通过&self&mut self参数获得对象上下文,方法通过::访问

  • lambda 表达式的语法比较特别,使用||包围参数表,函数体可以没有大括号{}。相对于函数来说,lambda 表达式更加自由,能够自动获取环境中的变量。

  • lambda 表达式与其他语言类似,可以作为函数的参数和返回值出现。但一些高级用法涉及到了后面的章节,这里先跳过。

  • rust 中存在一种类型叫做空类型,它的特点是没有任何可能的值,无法实例化。返回值为空类型的函数永远不会返回,文中指出一个可能会用到这个特性的地方是与网络请求有关的函数。

  • 模块(module)类似于 cpp 的命名空间,模块中可以存放任何内容,默认私有,通过pub()关键词可以声明成员公有及公有范围,成员通过self访问本模块成员,通过super访问外部模块成员。在外部通过::访问模块内的公有成员,mod关键词可以绑定其他文件的模块,use关键词可以绑定模块成员到上下文。

  • rust 将编译单元称为crate,crate 是经过预处理后生成的,与源代码的结构不完全相同

  • cargo是 rust 官方的中心化包管理器, crates.io 是平台,和其他包管理器一样支持项目管理、依赖管理、自定义脚本,还支持单元测试。

  • attribute类似于 c 语言的预处理器命令,但不同的是在 rust 中这部分由编译器处理,语法为#[]#![],功能包括但不限于提供 crate 元信息、配置编译选项、条件编译、标记单元测试、标记基准测试。

match 用例

let number = 13;

match number {
// Match a single value
1 => println!("One!"),
// Match several values
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
// Match an inclusive range
// Bind to `n` for the sequence of 13 ..= 19
n @ 13..=19 => println!("A teen of age {}", n),
// Handle the rest of cases
_ => println!("Ain't special"),
}

let triple = (0, -2, 3);

// A match block can destructure items in a variety of ways
match triple {
// A match guard can be added to filter the arm
(0, y, z) => if y == z => println!("These are twins"),
// Destructure the second and third elements
(0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
(1, ..) => println!("First is `1` and the rest doesn't matter"),
// `..` can be the used ignore the rest of the tuple
_ => println!("It doesn't matter what they are"),
// `_` means don't bind the value to a variable
}